using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.IO;


namespace Framework.Network
{
    public sealed class Session : BaseEntity
    {
        private static int s_RpcId { get; set; }
        private BaseChannel m_Channel;
        private readonly Dictionary<int, Action<IResponse>> m_Id2RequestCallBack = new Dictionary<int, Action<IResponse>>();
        private readonly byte[] m_OpcodeBytes = new byte[2];

        public Session(BaseChannel channel) : base()
        {
            this.Id = IdGenerater.GenerateId("Session");
            this.m_Channel = channel;
            int id = this.Id;
            channel.ErrorCallBack += (c, e) =>
            {
                this.Network.Remove(id);
            };
            channel.ReadCallBack += this.OnRead;
        }

        public NetworkManager Network
        {
            get
            {
                return this.GetParent<NetworkManager>();
            }
        }

        public int Error
        {
            get
            {
                return this.m_Channel.Error;
            }
            set
            {
                this.m_Channel.Error = value;
            }
        }

        public override void Dispose()
        {
            if (this.IsDisposed)
            {
                return;
            }
            this.Network.Remove(this.Id);
            foreach (Action<IResponse> action in this.m_Id2RequestCallBack.Values.ToArray())
            {
                action.Invoke(new ErrorResponse { Error = this.Error });
            }
            this.m_Channel.Dispose();
            this.m_Id2RequestCallBack.Clear();
            base.Dispose();
        }

        public void Start()
        {
            this.m_Channel.Start();
        }

        public IPEndPoint RemoteAddress
        {
            get
            {
                return this.m_Channel.RemoteAddress;
            }
        }

        public ChannelType ChannelType
        {
            get
            {
                return this.m_Channel.ChannelType;
            }
        }

        public MemoryStream Stream
        {
            get
            {
                return this.m_Channel.Stream;
            }
        }

        public void OnRead(MemoryStream memoryStream)
        {
            try
            {
                this.Run(memoryStream);    
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

        private void Run(MemoryStream memoryStream)
        {
            memoryStream.Seek(Packet.MessageIndex, SeekOrigin.Begin);
            ushort opcode = BitConverter.ToUInt16(memoryStream.GetBuffer(), Packet.OpcodeIndex);
            object message;
            try
            {
                object instance = this.Network.GetMessage(opcode);
                message = this.Network.MessagePacker.DeserializeFrom(instance, memoryStream);
            }
            catch (Exception e)
            {
                Log.Error($"opcode: {opcode} {e}, ip: {this.RemoteAddress}");
                this.Error = ErrorCode.ERR_PacketParserError;
                this.Network.Remove(this.Id);
                return;
            }

            if (!(message is IResponse response))
            {
                this.Network.DispatcherMessage(this, opcode, message);
                return;
            }

            Action<IResponse> action;
            if (!this.m_Id2RequestCallBack.TryGetValue(response.RpcId, out action))
            {
                throw new Exception($"not found rpc, response message: ");
            }
            this.m_Id2RequestCallBack.Remove(response.RpcId);
            action(response);
        }

        public FTask<IResponse> Call(IRequest request)
        {
            int rpcId = ++s_RpcId;
            var tcs = new FTaskCompletionSource<IResponse>();
            this.m_Id2RequestCallBack[rpcId] = (response) =>
            {
                if (ErrorCode.IsRpcNeedThrowException(response.Error))
                {
                    tcs.SetException(new Exception($"Rpc Error: {request.GetType().FullName} {response.Error}"));
					return;
                }
                tcs.SetResult(response);
            };
            request.RpcId = rpcId;
            this.Send(request);
            return tcs.Task;
        }

        public void Send(IMessage message)
        {
            ushort opcode = this.Network.GetOpcode(message.GetType());
            this.Send(opcode, message);
        }

        public void Send(ushort opcode, object message)
        {
            MemoryStream stream = this.Stream;
            stream.Seek(Packet.MessageIndex, SeekOrigin.Begin);
            stream.SetLength(Packet.MessageIndex);
            this.Network.MessagePacker.SerializeTo(message, stream);
            stream.Seek(0, SeekOrigin.Begin);
            this.m_OpcodeBytes.WriteTo(0, opcode);
            Array.Copy(this.m_OpcodeBytes, 0, stream.GetBuffer(), 0, m_OpcodeBytes.Length);
            this.Send(stream);
        }

        public void Send(MemoryStream stream)
        {
            this.m_Channel.Send(stream);
        }
    }
}